/*
 * Copyright (C) 2008,2009,2010,2011  Paulo Cesar Pereira de Andrade.
 *
 * This is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Authors:
 *	Paulo Cesar Pereira de Andrade
 */

#include "exl.h"

/*
 * Implementation
 */
esymbol_t *
eget_field(erecord_t *record, evector_t *name)
{
    return ((esymbol_t *)eget_hash(record->fields, name));
}

esymbol_t *
enew_field(erecord_t *record, evector_t *name, etag_t *tag)
{
    erecord_t	*type;
    eint32_t	 size;
    esymbol_t	*field;
    eint32_t	 offset;

    if ((field = eget_field(record, name)) != null)
	eparse_error(null, "duplicated field '%p'", name);

    field = enew_symbol(record->fields, name);
    field->a_field = true;
    if (tag) {
	field->tag  = tag;
	field->type = etag_packed_p(tag);
	type = tag->data;
    }
    else
	type = null;

    switch (etype(record)) {
	case t_struct:
	case t_class:
	    if (field->type)
		size = type->size;
	    else
		size = sizeof(eobject_t);
	    offset = record->size;
	    switch (size) {
		case 1: break;
		case 2:
		    offset = (offset + 1) & ~1; break;
#if ALIGN64
		case 4:
#else
		default:
#endif
		    offset = (offset + 3) & ~3; break;
#if ALIGN64
		default:
		    offset = (offset + 7) & ~7; break;
#endif
	    }
	    record->size = offset + size;
	    field->offset = offset;
	    if (field->type == t_void) {
		if (record->offsets == null)
		    evector(&record->offsets, t_int32, 4, 1);
		else if ((record->offsets->offset & 3) == 0)
		    erenew_vector(record->offsets, record->offsets->offset + 4);
		record->offsets->v.i32[record->offsets->offset++] = offset;
	    }
	    if (record->type == t_pointer)
		edef_record(record);
	    break;
	default:
	    assert(etype(record) == t_struct || etype(record) == t_class ||
		   etype(record) == t_union);
	case t_union:
	    break;
    }

    if (record->vfield == null)
	evector(&record->vfield, t_void, 4, 1);
    else if ((record->vfield->offset & 3) == 0)
	erenew_vector(record->vfield, record->vfield->offset + 4);
    record->vfield->v.ptr[record->vfield->offset++] = field;

    return (field);
}

erecord_t *
eadd_record(erecord_t *record, erecord_t *super)
{
    eint32_t	  i;
    etype_t	  type;
    etype_t	  kind;
    esymbol_t	**list;

    type = etype(super);
    kind = etype(record);
    if (type < t_struct || type > t_union)
	eparse_error(null, "object is not a record");
    if (type != kind)
	eparse_error(null, "'%s' cannot inherit a '%s'",
		     erecord_token_string(kind - t_struct + tok_struct),
		     erecord_token_string(type - t_struct + tok_struct));
    switch (kind) {
	case t_struct:
	case t_class:
	    /* if first added record, this is the direct superclass */
	    assert(record->super == null || record->fields->count == 0);
	    record->super = super;
	    /* FALLTHROUGH */
	case t_union:
	    break;
	default:
	    assert(kind == t_struct || kind == t_class || kind == t_union);
	    break;
    }
    if (type == t_union) {
	if (super->fields->count == 0)
	    eparse_error(null, "'%p' is not defined", super->name->name);
    }
    else if (super->type & t_pointer)
	eparse_error(null, "'%p' is not defined", super->name->name);

    /* fields with the same name are a fatal error */
    list = super->vfield->v.obj;
    for (i = 0; i < super->vfield->offset; i++)
	enew_field(record, list[i]->name, list[i]->tag);

    if (kind != t_union && record->type == t_pointer)
	edef_record(record);

    return (record);
}

erecord_t *
enew_basic(esymbol_t *name, eint32_t size)
{
    etag_t	*tag;
    ertti_t	*rtti;
    erecord_t	*record;

    /* enew_basic must be called only at startup */
    enew(&record, basic);
    record->type = ets_idx;
    record->name = name;
    record->size = size;
    name->a_type = true;
    tag = etag_object(record);
    name->tag = tag;
    name->value = tag;
    tag->name = name;

    rtti = ets + ets_idx;
    rtti->size = size;
    record_vector[ets_idx] = record;
    ++ets_idx;

    return (record);
}

erecord_t *
enew_record(esymbol_t *name, etype_t type)
{
    etag_t	*tag;
    erecord_t	*record;

    /* type is initialized to an "invalid" but known value, that also
     * happens to be treated as an object pointer by code in edata.c */
    switch (type) {
	case t_struct:		case t_class:
	    enew_object((eobject_t *)&record, type, sizeof(erecord_t));
	    record->type = t_pointer;
	    break;
	default:
	    assert(type == t_struct || type == t_class || type == t_union);
	case t_union:
	    enew(&record, union);
	    break;
    }
    v_check(1);
    v_push(record);
    record->name = name;
    name->a_type = true;
    tag = etag_object(record);
    name->tag = tag;
    name->value = tag;
    tag->name = name;
    enew_hash((eobject_t *)&record->fields, H_pointer, 5);
    record->fields->parent = ht_current;
    v_dec();

    return (record);
}

erecord_t *
eend_record(erecord_t *record)
{
    etype_t		  kind;
    ertti_t		 *rtti;
    eint32_t		  count;
    esymbol_t		**vfield;

    kind = etype(record);
    switch (kind) {
	case t_class:
	case t_struct:
#if __WORDSIZE == 32
	    record->size = (record->size + 3) & ~3;
#else
	    record->size = (record->size + 7) & ~7;
#endif
	    if (record->type == t_pointer)
		edef_record(record);
	    record->type &= ~t_pointer;
	    rtti = ets + record->type;
	    rtti->size = record->size;
	    enew_object((eobject_t *)&rtti->translate, t_void,
			sizeof(eint32_t) * rtti->size);
	    if (record->fields->count)
		enew_object((eobject_t *)&rtti->fields, t_void,
			    record->fields->count * sizeof(efield_t));
	    if (record->super) {
		count = ets[record->super->type].nfield;
		rtti->super = record->super->type;
		if (count) {
		    memcpy(rtti->fields, ets[record->super->type].fields,
			   count * sizeof(efield_t));
		    memcpy(rtti->translate, ets[record->super->type].translate,
			   record->super->size * sizeof(eint32_t));
		}
	    }
	    else
		count = 0;
	    if (record->vfield) {
		vfield = record->vfield->v.obj;
		for (; count < record->fields->count; count++) {
		    rtti->fields[count].parent = record->type;
		    rtti->fields[count].offset = vfield[count]->offset;
		    rtti->translate[vfield[count]->offset] = count;
		}
	    }
	    rtti->nfield = count;
	    if (record->offsets) {
		rtti->noffset = record->offsets->offset;
		rtti->offsets = record->offsets->v.i32;
	    }
	    else {
		rtti->noffset = 0;
		rtti->offsets = null;
	    }
	    break;
	default:
	    assert(kind == t_struct || kind == t_class || kind == t_union);
	case t_union:
	    break;
    }

    return (record);
}

void
edef_record(erecord_t *record)
{
    etype_t	kind;

    kind = etype(record);
    switch (kind) {
	case t_class:
	case t_struct:
	    if (record->type == t_pointer) {
		if (ets_idx >= ets_len) {
		    assert(ets_idx < (1 << 24));
		    ets_len += 4096;
		    eresize_object((eobject_t *)&ets,
				   ets_len * sizeof(eobject_t));
		    eresize_object((eobject_t *)&record_vector,
				   ets_len * sizeof(eobject_t));
		}
		/* allocate type, but flag as not yet finished */
		record->type = t_pointer | ets_idx;
		record_vector[ets_idx] = record;
		++ets_idx;
	    }
	    break;
	default:
	    break;
    }
}
